package com.gitee.jwds666.fs.controller;


import com.gitee.jwds666.fs.account.AccountAuth;
import com.gitee.jwds666.fs.config.FileShareConfig;
import com.gitee.jwds666.fs.core.FileConvertFileInfo;
import com.gitee.jwds666.fs.core.FileInfo;
import com.gitee.jwds666.fs.env.FileConstants;
import com.gitee.jwds666.fs.utils.FileTypeUtils;
import com.gitee.jwds666.fs.utils.FileUtils;


import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.List;

@RestController
public class BasicController {

    @Resource
    private FileShareConfig config;

    @Resource
    private AccountAuth accountAuth;

    @Resource
    private FileConvertFileInfo fileConvertFileInfo;



    private void assertPathCorrect(String path){
        if (!FileUtils.ensurePathCorrect(path)){
            throw new RuntimeException("路径不正确");
        }
    }




    @GetMapping("auth")
    public String auth(){
        return accountAuth.auth().getAccount();
    }


    @GetMapping("fileList")
    public Result fileList(String path){
        Map<String,Object> res = new HashMap<>();
        assertPathCorrect(path);
        FileInfo targetFileInfo = fileConvertFileInfo.convert(config.getBasePath()+path);
        res.put("insert",targetFileInfo.isInsert());
        res.put("fileInfos",new ArrayList<>());
        if (targetFileInfo.isSelect()){
            List<File> files = FileUtils.getFileList(targetFileInfo.getFile());
            List<FileInfo> fileInfos = fileConvertFileInfo.convertList(files);
            for (int i = 0; i < fileInfos.size(); i++) {
                if (!fileInfos.get(i).isSelect()){
                    fileInfos.remove(i);
                    i--;
                }
            }
            res.put("fileInfos",fileInfos);
        }
        return Result.success(res);
    }

    @PostMapping("upFile")
    public Result upFile(String path,MultipartFile file){
        assertPathCorrect(path);
        FileInfo fileInfo = assertUpFile(path,file.getOriginalFilename());
        if (!fileInfo.isExists() || fileInfo.isDelete()){
            try {
                FileCopyUtils.copy(file.getInputStream(),new FileOutputStream(fileInfo.getFile()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return Result.success();
    }



    @GetMapping("newDir")
    public Result newDir(String path,String dirName){
        assertPathCorrect(path);
        FileInfo fileInfo = assertUpFile(path,dirName);
        if (!fileInfo.isExists()){
            fileInfo.getFile().mkdir();
        }
        return Result.success();
    }


    public FileInfo assertUpFile(String path,String filename){
        FileInfo fileInfo = fileConvertFileInfo.convert(config.getBasePath()+path);
        if (!fileInfo.isExists()){
            throw new RuntimeException("目标文件夹不存在") ;
        }
        if (fileInfo.getIsFile()){
            throw new RuntimeException("目标不是文件夹");
        }
        if (!fileInfo.isInsert()){
            throw new RuntimeException("此账户不能添加");
        }
        String targetPath = config.getBasePath()+path+ FileConstants.SEPARATOR+filename;
        return fileConvertFileInfo.convert(targetPath);
    }

    @GetMapping("assertUpFiles")
    public Result assertUpFiles(String path,String filenames){
        assertPathCorrect(path);
        Map<String,Boolean> res= new LinkedHashMap<>();
        String[] filenameArr = filenames.split("\\|");
        for (String filename : filenameArr) {
            FileInfo fileInfo = assertUpFile(path,filename);
            if (fileInfo.isExists()){
                res.put(filename,fileInfo.isDelete());
            }
        }
        return Result.success(res);
    }


    @GetMapping("rename")
    public Result rename(String path,String newFilename){
        assertPathCorrect(path);
        if (!FileUtils.ensureFilenameCorrect(newFilename)){
            throw new RuntimeException("无效的名称");
        }
        File sourceFile = new File(config.getBasePath()+path);
        FileInfo fileInfo = fileConvertFileInfo.convert(sourceFile);
        if (fileInfo.isExists() && fileInfo.isUpdate()){
            String sourcePath = sourceFile.getPath();
            String targetPath = sourcePath.substring(0,sourcePath.lastIndexOf(FileConstants.SEPARATOR)+1)+newFilename;
            sourceFile.renameTo(new File(targetPath));
        }
        return Result.success();
    }

    @GetMapping("deleteFile")
    public Result deleteFile(String paths){
        String pathArr[] = paths.split("\\|");
        List<String> res = new ArrayList<>();
        for (String path : pathArr) {
            try {
                assertPathCorrect(path);
                FileInfo fileInfo = fileConvertFileInfo.convert(config.getBasePath()+path);
                if (fileInfo.isExists()){
                    if (fileInfo.getIsFile() && !fileInfo.isDelete()){
                        return Result.error("没有此文件的删除权限");
                    }else{
                        try {
                            Files.walkFileTree(fileInfo.getFile().toPath(),new SimpleFileVisitor<Path>(){

                                @Override
                                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                                    FileInfo deleteInfo = fileConvertFileInfo.convert(dir.toFile());
                                    if (deleteInfo.isDelete()){
                                        return FileVisitResult.CONTINUE;
                                    }
                                    throw new RuntimeException("没有此目录下所有文件的删除权限");
                                }

                                @Override
                                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                                    FileInfo deleteInfo = fileConvertFileInfo.convert(file.toFile());
                                    if (deleteInfo.isDelete()){
                                        return FileVisitResult.CONTINUE;
                                    }
                                    throw new RuntimeException("没有此目录下所有文件的删除权限");
                                }

                            });
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    FileSystemUtils.deleteRecursively(fileInfo.getFile());
                }
            }catch (RuntimeException e){
                res.add(e.getMessage());
            }
        }
        if (res.size() != 0){
            return Result.error(res);
        }
        return Result.success();
    }



    @GetMapping("download")
    public void download(String path){
        assertPathCorrect(path);
        FileInfo fileInfo = fileConvertFileInfo.convert(config.getBasePath()+path);
        if (fileInfo.isExists() && fileInfo.getIsFile() && fileInfo.isSelect()){
            HttpServletResponse response = ((ServletRequestAttributes)(RequestContextHolder.getRequestAttributes())).getResponse();

            try {
                StringBuilder contentDispositionValue = new StringBuilder();
                String name =  URLEncoder.encode(fileInfo.getName(), StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
                contentDispositionValue.append("attachment; filename=")
                        .append(name)
                        .append(";")
                        .append("filename*=")
                        .append("utf-8''")
                        .append(name);
                response.setHeader("Content-disposition", contentDispositionValue.toString());
                response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
                response.setHeader("Content-length", fileInfo.getSize()+"");
                FileCopyUtils.copy(new FileInputStream(fileInfo.getFile()),response.getOutputStream());
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @GetMapping("open/{filename}")
    public void open(@PathVariable("filename") String filename, String path, HttpServletRequest request, HttpServletResponse response){
        assertPathCorrect(path);
        FileInfo fileInfo = fileConvertFileInfo.convert(config.getBasePath()+path);
        if (fileInfo.isExists() && fileInfo.getIsFile() && fileInfo.isSelect()){
            try {
                MediaType type = FileTypeUtils.getMediaType(fileInfo.getFile());
                if (type == null)return;
                long fileSize = fileInfo.getSize();
                response.setContentType(type.getType()+"/"+type.getSubtype());
                response.setHeader("Content-disposition", "inline");
                String rangeString = request.getHeader("Range");//如果是video标签发起的请求就不会为null
                if (rangeString != null){
                    if (rangeString.split(",").length > 1){
                        response.setStatus(416);
                        return;
                    }
                    long[] startEnd = parseRange(rangeString,fileSize);
                    long start = startEnd[0],end = startEnd[1];
                    response.setContentLength((int) (end-start+1));
                    response.setStatus(206);
                    response.setHeader("Content-Range","bytes "+start+"-"+end+"/"+fileSize);
                    response.setHeader("Accept-Ranges", "bytes");
                    response.setHeader("ETag", String.valueOf(fileInfo.getUpdateTime().getTime()));
                    outResponse(fileInfo.getFile(),start,end,response);
                }else{

                    response.setHeader("Content-length", fileSize+"");
                    InputStream is = new FileInputStream(fileInfo.getFile());
                    FileCopyUtils.copy(is,response.getOutputStream());
                }

            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private long[] parseRange(String rangeStr,long fileSize){
        String[] rangeStartEnd =rangeStr.substring(rangeStr.indexOf("=") + 1).split("-");
        long start,end = fileSize-1;
        if (rangeStartEnd.length == 2){
            if (rangeStartEnd[0].equals("")){
                start = fileSize - Long.parseLong(rangeStartEnd[1]);
            }else{
                start = Long.parseLong(rangeStartEnd[0]);
                end = Long.parseLong(rangeStartEnd[1]);
            }
        }else{
            start = Long.parseLong(rangeStartEnd[0]);
        }
        return new long[]{start,end};
    }

    private void outResponse(File file,long start,long end,HttpServletResponse response) throws IOException {
        RandomAccessFile randomAccessFile = new RandomAccessFile(file,"r");
        randomAccessFile.seek(start);
        int oneSize = 1024*512;
        byte[] bytes = new byte[oneSize];
        while(randomAccessFile.getFilePointer()+oneSize <= end){
            randomAccessFile.read(bytes);
            response.getOutputStream().write(bytes);
        }
        bytes = new byte[(int) (end - randomAccessFile.getFilePointer() +1)];
        randomAccessFile.read(bytes);
        randomAccessFile.close();
        response.getOutputStream().write(bytes);
    }




    @GetMapping("paste")
    public Result paste(final String sourcePaths,final String targetPath,boolean deleteSource,boolean replace){
        try {
            assertPathCorrect(targetPath);
            FileInfo target = fileConvertFileInfo.convert(config.getBasePath()+targetPath);
            if (!target.isExists()){
                return Result.error("目标文件夹不存在");
            }
            if (!target.isInsert()){
                return Result.error("没有目标文件夹的添加权限");
            }
            String[] sourcePathArr = sourcePaths.split("\\|");
            for (String sourcePath : sourcePathArr) {
                assertPathCorrect(sourcePath);

                FileInfo source = fileConvertFileInfo.convert(config.getBasePath()+sourcePath);

                if (!source.isExists()){
                    return Result.error("复制源不存在");
                }

                if (source.getFile().getParentFile().equals(target.getFile())){
                    return Result.success();
                }

                File targetFile = target.getFile();
                File sourceFile = source.getFile();
                while(targetFile != null){
                    if (targetFile.equals(sourceFile)){
                        return Result.error("目标文件夹是源文件夹的子文件夹");
                    }
                    targetFile = targetFile.getParentFile();
                }

                final String sourceBasePath = source.getFile().getPath().substring(0,source.getFile().getPath().lastIndexOf(FileConstants.SEPARATOR));
                Files.walkFileTree(source.getFile().toPath(), new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                        FileInfo sourceDirInfo = fileConvertFileInfo.convert(dir.toFile());
                        FileInfo targetDirInfo = fileConvertFileInfo.convert(target.getFile().getPath()+sourceDirInfo.getFile().getPath().substring(sourceBasePath.length()));
                        if (!sourceDirInfo.isSelect() || !targetDirInfo.isInsert()){
                            return FileVisitResult.SKIP_SUBTREE;
                        }
                        if (!targetDirInfo.isExists()){
                            targetDirInfo.getFile().mkdir();
                        }
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        FileInfo sourceFileInfo = fileConvertFileInfo.convert(file.toFile());
                        FileInfo targetFileInfo = fileConvertFileInfo.convert(target.getFile().getPath()+sourceFileInfo.getFile().getPath().substring(sourceBasePath.length()));
                        if (sourceFileInfo.isSelect() && (!targetFileInfo.isExists() || replace && targetFileInfo.isDelete())){
                            if (!targetFileInfo.isExists()){
                                targetFileInfo.getFile().createNewFile();
                            }
                            FileCopyUtils.copy(new FileInputStream(sourceFileInfo.getFile()),new FileOutputStream(targetFileInfo.getFile()));
                            if (deleteSource && sourceFileInfo.isDelete()){
                                sourceFileInfo.getFile().delete();
                            }
                        }
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                        FileInfo sourceFileInfo = fileConvertFileInfo.convert(dir.toFile());
                        if (deleteSource && sourceFileInfo.isDelete()){
                            sourceFileInfo.getFile().delete();
                        }
                        return FileVisitResult.CONTINUE;
                    }
                });
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return Result.success();
    }

    @GetMapping("version")
    @ResponseBody
    public Object version(){
        return config;
    }


    String shareText = "共享文本域";
    @GetMapping("shareText")
    @ResponseBody
    public String shareText(){
        return shareText;
    }

    @PostMapping("setShareText")
    @ResponseBody
    public void shareText(String shareText){
        this.shareText = shareText;
    }
}
